// Copyright 2011, Vanya Davidenko.
// Используемая кодировка: UTF-8.

#ifndef LIBV_FILE_BASIC_FILE_HPP_
#define LIBV_FILE_BASIC_FILE_HPP_

#include <assert.h>
#include <cstdio>
#include <limits>
#include <string>
#include <libv/cast.h>
#include <libv/check.h>
#include <libv/error_msg.h>
#include <libv/file/basic_file.h>



namespace libv
{

BasicFile::BasicFile()
    : prealloc_(PreallocType::kRecPrealloc, 0)
{
    Reset();

    auto_remove_ = false;
    last_error_ = LIBV_FILE_NO_ERROR;
}



bool BasicFile::is_opened() const
{
    return file_handle_ != LIBV_FILE_INVALID_HANDLE;
}



bool BasicFile::Close() const
{
    if ( !is_opened() )
    {
        last_error_ = LIBV_FILE_ERROR_INVALID_HANDLE;
        return false;
    }

    Unlock();
    const bool result = CloseImpl();
    Reset();
    return result;
}



bool BasicFile::Close()
{
    if ( !is_opened() )
    {
        last_error_ = LIBV_FILE_ERROR_INVALID_HANDLE;
        return false;
    }


    // last_error_ заполняется по наиболее значимой ошибке.
    bool result = true;
    if ( auto_remove_ )
    {
        Unlock();
        CloseImpl();
        result = ( ::std::remove(file_path_.c_str()) != -1 );
        last_error_ = static_cast<ErrorCode>(errno);
    }

    // В линуксе во всяком случае fsync при close не делается, страхуемся.
    else if ( Flush() )
    {
        Unlock();  // Unlock после Flush
        result = CloseImpl();
    }

    else  // Ошибка в Flush(), который уже заполнил last_error_
    {
        Unlock();
        CloseImpl();
        result = false;
    }

    Reset();
    return result;
}



fpos64 BasicFile::Size() const
{
    if ( !is_opened() )
    {
        last_error_ = LIBV_FILE_ERROR_INVALID_HANDLE;
        return -1;
    }

    // Зачем каждый раз делать системный вызов, если можно закэшировать размер.
    if ( file_size_ != -1 )
    {
        // Проверка, на случай, если не был вызван PreallocateAtLeast()
        // в методе Write() наследника.
        assert(file_size_ == SizeImpl());
        return file_size_;
    }

    file_size_ = SizeImpl();
    return file_size_;
}



bool BasicFile::Resize(const fpos64 size)
{
    if ( !is_opened() || read_only() )
    {
        last_error_ = LIBV_FILE_ERROR_INVALID_HANDLE;
        return false;
    }

    if ( !ResizeImpl(size) )
        return false;

    file_size_ = size;
    return true;
}



bool BasicFile::Flush()
{
    if ( !is_opened() || read_only() )
    {
        last_error_ = LIBV_FILE_ERROR_INVALID_HANDLE;
        return false;
    }

    return FlushImpl();
}



bool BasicFile::TryLock(const int lock_type) const
{
    return BasicLock(lock_type, false);
}



bool BasicFile::Lock(const int lock_type) const
{
    return BasicLock(lock_type, true);
}



bool BasicFile::Unlock() const
{
    // В Windows, в отличие от Linux нельзя вызывать Unlock на
    // незаблокированный файл, не будем тратить системный вызов на это
    if ( lock_type() == LockType::kNoLock )
        return true;

    if ( !UnlockImpl() )
        return false;

    lock_type_ = LockType::kNoLock;
    return true;
}



void BasicFile::set_auto_remove(const bool auto_remove)
{
    auto_remove_ = auto_remove;
}



bool BasicFile::set_prealloc(const Prealloc& prealloc)
{
    if ( !is_opened() || read_only() )
    {
        last_error_ = LIBV_FILE_ERROR_INVALID_HANDLE;
        return false;
    }

    if ( !PreallocType::is_valid(prealloc.type) )
    {
        last_error_ = LIBV_FILE_ERROR_BAD_ARGUMENTS;
        return false;
    }

    prealloc_ = prealloc;
    return true;
}



FileHandle BasicFile::file_handle() const
{
    return file_handle_;
}



ErrorCode BasicFile::last_error() const
{
    return last_error_;
}



int BasicFile::lock_type() const
{
    return lock_type_;
}



::std::string BasicFile::file_path() const
{
    return file_path_;
}



bool BasicFile::read_only() const
{
    return read_only_;
}



bool BasicFile::auto_remove() const
{
    return auto_remove_;
}



BasicFile::Prealloc BasicFile::prealloc() const
{
    return prealloc_;
}



///////////////////////////////// PROTECTED ////////////////////////////////////



// Запрещаем удаление через базовый класс.
BasicFile::~BasicFile()
{
    // Сам деструктор игнорирует константность.
    if ( is_opened() )
    {
        bool result = true;
        if ( read_only() )
            result = ((const BasicFile*)this)->Close();
        else
            result = ((BasicFile*)this)->Close();

        if ( !result )
        {
            ::std::fprintf(stderr, "BasicFile::~BasicFile() failed: %s",
                           OsErrorCode2String(last_error_).c_str());
        }
    }
}



bool BasicFile::BasicOpen(const ::std::string& file_path,
                          const int open_type,
                          const bool enable_read_ahead_caching,
                          const int additional_flags,
                          const bool read_only) const
{
    if ( is_opened() )
    {
        last_error_ = LIBV_FILE_ERROR_INVALID_HANDLE;
        return false;
    }

    if ( !OpenType::is_valid(open_type) )
    {
        last_error_ = LIBV_FILE_ERROR_BAD_ARGUMENTS;
        return false;
    }
    assert(open_type == OpenType::kCreateIfNotExist ||
           open_type == OpenType::kOpenAlways ||
           open_type == OpenType::kOpenExisting);

    const FileHandle file_handle = BasicOpenImpl(file_path,
                                                 open_type,
                                                 enable_read_ahead_caching,
                                                 additional_flags,
                                                 read_only);

    if ( file_handle == LIBV_FILE_INVALID_HANDLE )
        return false;

    file_handle_ = file_handle;
    file_size_ = Size();
    file_path_ = file_path;
    read_only_ = read_only;

    return true;
}



bool BasicFile::BasicLock(const int lock_type, const bool block) const
{
    if ( !is_opened() )
    {
        last_error_ = LIBV_FILE_ERROR_INVALID_HANDLE;
        return false;
    }

    if ( !LockType::is_valid(lock_type) || lock_type == LockType::kNoLock )
    {
        last_error_ = LIBV_FILE_ERROR_BAD_ARGUMENTS;
        return false;
    }
    assert(lock_type == LockType::kSharedLock ||
           lock_type == LockType::kExclusiveLock);


    // В Windows файл может иметь 2 блокировки, но исходя из семантики
    // данного класса, снимаем предыдущую блокировку, симулируя замену.
    // Если на файле две блокировки, нужно вызывать 2 раза Unlock.
    // Windows в отличие от Linux не поддерживает их атомарное переключение.
    if ( lock_type_ == lock_type )
        return true;

    if ( !Unlock() || !BasicLockImpl(lock_type, block) )
        return false;

    lock_type_ = lock_type;
    return true;
}



bool BasicFile::PreallocateAtLeast(const fpos64 max_offset)
{
    if ( !is_opened() || read_only() )
    {
        last_error_ = LIBV_FILE_ERROR_INVALID_HANDLE;
        return false;
    }

    if ( max_offset < 0 )
    {
        last_error_ = LIBV_FILE_ERROR_BAD_ARGUMENTS;
        return false;
    }


    if ( max_offset - file_size_ > 0 )
    {
        const fpos64 s_prealloc_param = IntegerCast<fpos64>(prealloc_.param);

        if ( prealloc_.type == PreallocType::kRecPrealloc )
            return Resize(max_offset);

        else if ( prealloc_.type == PreallocType::kConstIncPrealloc )
            return Resize(max_offset + s_prealloc_param);

        else if ( prealloc_.type == PreallocType::kPercIncPrealloc )
            return Resize(max_offset * ( 100 + s_prealloc_param ) / 100);

        VERIFY(0);
        return false;
    }
    return true;
}



/////////////////////////////////// PRIVATE ////////////////////////////////////



void BasicFile::Reset() const
{
    file_handle_ = LIBV_FILE_INVALID_HANDLE;
    file_size_ = -1;
    lock_type_ = LockType::kNoLock;
    file_path_.clear();
    read_only_ = false;
}

}  // libv

#endif  // LIBV_FILE_BASIC_FILE_HPP_
